#include "token.h"

std::vector<token> scan_tokens(std::string input) {
    token_scanner ts(input);
    ts.scan_tokens();
    return ts.get_tokens();
}
token::token(tokentype type, unsigned int count, std::string varname) : type(type), count(count), varname(varname) {}
token::token(const token &t) : token(t.type, t.count, t.varname) {}

std::string token::to_string() {
    std::stringstream bufstream;
    std::string type;
    if (this->type == tokentype::WORD) {
        type = "WORD";
    } else if (this->type == tokentype::LETTER) {
        type = "LETTER";
    } else if (this->type == tokentype::DIGIT) {
        type = "DIGIT";
    } else if (this->type == tokentype::NUMBER) {
        type = "NUMBER";
    } else if (this->type == tokentype::X) {
        type = "X";
    } else if (this->type == tokentype::CONST) {
        type = "CONST";
    } else if (this->type == tokentype::VAR) {
        type = "VAR";
    }
    bufstream << type << " \t " << varname << " \t " << count << " \t ";
    return bufstream.str();
}

token_scanner::token_scanner(std::string input) : input(input), current(0) {}

// get current char and comsume it
char token_scanner::advance() {
    current++;
    return input[current - 1];
}
// get current letter and dont comsume it
char token_scanner::peek() {
    if (isEnd()) {
        return '\0';
    }
    return input[current];
}
bool token_scanner::isEnd() {
    return current >= input.length();
}

// convert input into tokens
void token_scanner::scan_tokens() {
    while (!isEnd()) {
        scan_token();
    }
}

std::vector<token> token_scanner::get_tokens() {
    return tokens;
}

std::string token_scanner::to_string() {
    std::stringstream bufstream;
    bufstream << "input: " << input << "\n";
    for (auto token : tokens) {
        bufstream << token.to_string() << '\n';
    }
    return bufstream.str();
}

// get first token
void token_scanner::scan_token() {
    char c = advance();
    if (c == '<') {
        std::string varname = take_varname();
        c = advance();
        if (c == '>') {
            push_varname(varname); // output var
        } else if (c != ':') {
            throw std::string("except type declaration or >\nthe declaration should be like \"<varname:type>\n");
        } else {
            c = advance();
            switch (c) {
            case 'd':
                push_digit(varname);
                break;
            case 'n':
                push_number(varname);
                break;
            case 'l':
                push_letter(varname);
                break;
            case 'w':
                push_word(varname);
                break;
            case 'x':
                push_x(varname);
                break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                current--;
                push_varname_zfill(varname);
                break;
            default:
                throw std::string("excpet vartype");
            }
        }
    } else { // const
        current--;
        push_const();
    }
}

std::string token_scanner::take_varname() {
    char c = peek();
    std::string varname = "";
    while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9')) {
        varname += advance();
        c = peek();
    }
    return varname;
}

void token_scanner::push_varname(std::string varname) {
    tokens.push_back(token(tokentype::VAR, 1, varname));
}
void token_scanner::push_varname_zfill(std::string varname) {
    tokens.push_back(token(tokentype::VAR, take_count(), varname));
}
void token_scanner::push_digit(std::string varname) {
    tokens.push_back(token(tokentype::DIGIT, take_count(), varname));
}
void token_scanner::push_number(std::string varname) {
    if (advance() != '>') {
        throw std::string("excpet '>'");
    }
    tokens.push_back(token(tokentype::NUMBER, 1, varname));
}
void token_scanner::push_letter(std::string varname) {
    tokens.push_back(token(tokentype::LETTER, take_count(), varname));
}
void token_scanner::push_word(std::string varname) {
    if (advance() != '>') {
        throw std::string("excpet '>'");
    }
    tokens.push_back(token(tokentype::WORD, 1, varname));
}
void token_scanner::push_x(std::string varname) {
    tokens.push_back(token(tokentype::X, take_count(), varname));
}
void token_scanner::push_const() {
    std::string constval = "";
    char c = peek();
    while (c != '\0' && c != '<') {
        constval += advance();
        c = peek();
    }
    tokens.push_back(token(tokentype::CONST, 1, constval));
}
unsigned int token_scanner::take_count() {
    char c = peek();
    std::string numberstr = "";
    unsigned int number = 1;
    while (c >= '0' && c <= '9') {
        numberstr += advance();
        c = peek();
    }
    if (c == '>') {
        if (numberstr != "") {
            std::stringstream temp(numberstr);
            temp >> number;
        }
        advance();
    } else {
        throw std::string("except >");
    }
    return number;
}
